//#include "crossbit/common.h"
//#include "fcntl.h"
//#include "signal.h"
//#include "sys/time.h"
//#include "sys/wait.h"
//#include "sys/resource.h"
#include "crossbit/Interpreter.h"
#include "crossbit/MemoryImage.h"
#include "crossbit/ELFMemoryImage.h"
#include "crossbit/Profiler.h"
//#include "crossbit/SYSCALL.h"
#include "arch/mips/MIPSProcessorState.h"

using namespace crossbit;


// Implementation related header info
#include "arch/mips/MIPSSignalHandler.h"


// Use temp variables to store GLOBAL stuff
// Why ? <- For support nesting signals
#define SWITCH_GLOBAL_STUFF \
		XTUint32 ori_last_exit = GLOBAL::last_exit;	\
		XTUint32 ori_saved_esp = GLOBAL::saved_esp;	\
		XTUint32 ori_SUPER_BLOCK = GLOBAL::BUILD_SUPER_BLOCK; \
		XTUint32 ori_SIG_RET = GLOBAL::SIG_RET; \
		XTUint32 ori_next_address = GLOBAL::next_address;	\
		XTUint32 ori_return_address = GLOBAL::return_address;	\
		TBlock* ori_tblock_stub = GLOBAL::tblock_stub;	\
	    VBlock* ori_vblock_stub = GLOBAL::vblock_stub;	\
		std::stack<std::pair<XTMemAddr, XTMemAddr> > ori_return_stack;	\
		if (!GLOBAL::return_stack.empty())	\
		{									\
			ori_return_stack.push(GLOBAL::return_stack.top());	\
			GLOBAL::return_stack.pop();	\
		}	\
		GLOBAL::last_exit = 0;	\
		GLOBAL::saved_esp = 0;	\
		GLOBAL::BUILD_SUPER_BLOCK = 0;	\
		GLOBAL::SIG_RET = 0;	\
		GLOBAL::next_address = 0;	\
		GLOBAL::return_address = 0;	\
		GLOBAL::tblock_stub = NULL;	\
		GLOBAL::vblock_stub = NULL;


#define RESTORE_GLOBAL_STUFF	\
		if (!ori_return_stack.empty())	\
		{								\
			GLOBAL::return_stack.push(ori_return_stack.top());	\
			ori_return_stack.pop();	\
		}	\
		GLOBAL::last_exit = ori_last_exit;	\
		GLOBAL::saved_esp = ori_saved_esp;	\
		GLOBAL::BUILD_SUPER_BLOCK = ori_SUPER_BLOCK;	\
		GLOBAL::SIG_RET = ori_SIG_RET;	\
		GLOBAL::next_address = ori_next_address;	\
		GLOBAL::return_address = ori_return_address;	\
		GLOBAL::tblock_stub = ori_tblock_stub;	\
		GLOBAL::vblock_stub = ori_vblock_stub;

MIPSSignalHandler::~MIPSSignalHandler()
{
}

void MIPSSignalHandler::signal_handler(XTInt32 local_sig_num)
{
        UTIL::XTLOG("signal_handler is invoked\n");
		UTIL::XTExit(-1);
}


void MIPSSignalHandler::rt_signal_handler(XTInt32 local_sig_num)
{
		ALL_SIG_OFF
        //UTIL::XTLOG("\n\nrt_signal_handler is invoked\n");
		SWITCH_GLOBAL_STUFF

		XTInt32 mips_sig_num = convertSigNum2Scr(local_sig_num);
		if (UNDEF == mips_sig_num)
		{
			UTIL::XTLOG("sig_num error!!\n");
			return;
		}
			
		XTMemAddr mips_handler = sig_rg->getHandlerSPC(mips_sig_num);
		if (!mips_handler)
		{
			UTIL::XTLOG("Handler address not exist!!\n");
			return;
		}

		XTUint8 sig_stack[MIPS_SIGSTKSZ + 4];
		XTMemAddr sig_stkaddr = ((XTMemAddr)((XTUint8*)sig_stack+MIPS_SIGSTKSZ)) & (~0x11);

		sig_state->switchRegs();

		sig_state->put(4,mips_sig_num);		// $a0 <- parameter
		sig_state->put(25,mips_handler);	// $t9
		sig_state->put(29,sig_stkaddr);		// $sp
		sig_state->put(30,sig_stkaddr);		// $s8|fp
		sig_state->put(31,GLOBAL::sig_ret_addr);		// $ra <- return to (sig_ret_addr) code after done

		doSignal(mips_handler);
		
		sig_state->restoreRegs();
		RESTORE_GLOBAL_STUFF
        //UTIL::XTLOG("\nexit from rt_signal_handler\n\n");
		ALL_SIG_ON

}


void MIPSSignalHandler::sigaction_handler(XTInt32 local_sig_num, struct siginfo *local_sig_info, void *unused)
{
        UTIL::XTLOG("sigaction_handler is invoked\n");
		UTIL::XTExit(-1);
}


void MIPSSignalHandler::rt_sigaction_handler(XTInt32 local_sig_num, struct siginfo *local_sig_info, void *unused)
{
        UTIL::XTLOG("rt_sigaction_handler is invoked\n");
		UTIL::XTExit(-1);
}

XTInt32 MIPSSignalHandler::convertSigNum2Scr(XTInt32 local_sig_num)
{
	if (local_sig_num > SIGRTMAX)
		return UNDEF;
	if (local_sig_num > SIGRTMIN)
		return local_sig_num;
	for (XTInt32 i=0; sig_table[i].mips_flag; i++)
	{
		if (local_sig_num == sig_table[i].local_flag)
			return sig_table[i].mips_flag;
	}

	return UNDEF;
}


void MIPSSignalHandler::doSignal(XTMemAddr sig_entry)
{
		TBlock *pre_tblock = NULL;
		VBlock *pre_vblock = NULL;

		//XTMemAddr gp = 0; // FIXME: obsolete??

		while(1)
		{
			TBlock *tblock = sig_tc -> lookupTBlock(sig_entry);
			VBlock *vblock;
        
			if (tblock == NULL) {
				sig_vbb->getReady(sig_entry);
				XTMemAddr next = sig_entry;

				while( !sig_vbb->isEnd() )
					next = sig_itp -> decode(next, sig_vbb);
				
				vblock = sig_vbb -> wrapUp(next);


				tblock = sig_tc -> requestTBlock();
				sig_tr -> encode(vblock, tblock);    
				sig_tc -> submitTBlock(sig_entry,tblock,vblock);

				while( GLOBAL::return_stack.size() != 0 ) {
					std::pair<XTMemAddr , XTMemAddr> return_pair = GLOBAL::return_stack.top() ;
					GLOBAL::return_stack.pop();
					XTMemAddr mem_addr = return_pair.first;
					XTMemAddr return_addr = return_pair.second;
					TBlock *temp_tblock = sig_tc -> lookupTBlock( return_addr ) ;
					if( temp_tblock == NULL ) {
							VBlock *temp_vblock;
							sig_vbb -> getReady( return_addr );
							XTMemAddr next = return_addr;

							while( !sig_vbb->isEnd() )
								next = sig_itp -> decode(next, sig_vbb);
						
							temp_vblock = sig_vbb -> wrapUp(next);

							VFilter::runFilters(temp_vblock);
							temp_vblock -> setEndOfCall( GLOBAL::checkCall( temp_vblock ) );

							temp_tblock = sig_tc -> requestTBlock();
							sig_tr -> encode(temp_vblock, temp_tblock);    
							sig_tc -> submitTBlock(return_addr,temp_tblock,temp_vblock);
						}		
     
					*((XTMemAddr*)mem_addr) = temp_tblock -> enterAddr();
				}
	
			}					 

			XTMemAddr enter = tblock->enterAddr();

#ifdef LINK
			if( pre_tblock != NULL ){
					Exit* exit = GLOBAL::getExit(GLOBAL::last_exit);
	
					pre_vblock = sig_tc -> lookupVBlock( pre_tblock -> getSPC() ); 
					if( pre_vblock -> isEndOfCall() ) {
							sig_tm-> callLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
					} else if( exit -> link_type == LinkType::DIRECT ){
							sig_tm -> directLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
					}
					else if( exit -> link_type == LinkType::INDIRECT ){
							sig_tm -> indirectLinkTo( pre_tblock , GLOBAL::last_exit , tblock );
					}
			}	
#endif
			ALL_SIG_ON
			CONTEXT_SAVE
				__asm__ __volatile__(
				        "mov %%esp , %%eax\n\t"
					    "sub $0x4, %%eax\n\t"
						"mov %%eax , %0\n\t"
						:
						:"m"(GLOBAL::saved_esp));  // save the current esp to the GLOBAL::save_esp;
			((void (*)()) enter) ();
				__asm__ __volatile__(
						"mov %%ebx , %0\n\t"
						"mov %%edx , %1\n\t"
						:
						:"m"(GLOBAL::last_exit) , "m"(GLOBAL::next_address));
			CONTEXT_RESTORE
			ALL_SIG_OFF

			if( GLOBAL::BUILD_SUPER_BLOCK ){
					// printf("build super_block\n");
					GLOBAL::tblock_stub = (TBlock *) GLOBAL::BUILD_SUPER_BLOCK; 
					GLOBAL::BUILD_SUPER_BLOCK = 0;
					sig_entry = GLOBAL::tblock_stub -> getSPC();
					TBlock* former_tblock = sig_tc -> lookupTBlock( sig_entry );
					VBlock *super_vblock = Profiler::buildSuperVBlock( sig_tc , sig_entry ); 	
					if( super_vblock == NULL ){
							super_vblock = sig_tc -> lookupVBlock( sig_entry );
					}
					TBlock *super_tblock = sig_tc -> requestTBlock();
					super_tblock -> setSuperBlock();
					sig_tr -> encode( super_vblock , super_tblock ); 

					sig_tc -> submitTBlock( sig_entry , super_tblock , super_vblock );						
					sig_tm-> linkSuperBlock( former_tblock , super_tblock );
					pre_tblock = NULL;	
					while( GLOBAL::return_stack.size() != 0 ) {
							std::pair<XTMemAddr , XTMemAddr> return_pair = GLOBAL::return_stack.top() ;
							GLOBAL::return_stack.pop();
							XTMemAddr mem_addr = return_pair.first;
							XTMemAddr return_addr = return_pair.second;
							TBlock *temp_tblock = sig_tc -> lookupTBlock( return_addr ) ;
							if( temp_tblock == NULL ) {
									VBlock *temp_vblock;
									sig_vbb -> getReady( return_addr );
									XTMemAddr next = return_addr;

									while( !sig_vbb->isEnd() )
											next = sig_itp -> decode(next, sig_vbb);
								
									temp_vblock = sig_vbb -> wrapUp(next);

									temp_tblock = sig_tc -> requestTBlock();
									sig_tr -> encode(temp_vblock, temp_tblock);    
									sig_tc -> submitTBlock(return_addr,temp_tblock,temp_vblock);
							}
		
							*((XTMemAddr*)mem_addr) = temp_tblock -> enterAddr();
					
					}					 
				
					continue;
			}

			//printf("next_address: %x\n",GLOBAL::next_address);
			// To judge whether signal handler is finished or not
			//if (~0x0 == GLOBAL::next_address)
			//if ((switch_addr == GLOBAL::next_address) || (GLOBAL::last_exit >= GLOBAL::total_exit))
			if (GLOBAL::SIG_RET)
				break;

			Exit *exit = GLOBAL::getExit(GLOBAL::last_exit);
			GLOBAL::tblock_stub = exit -> tblock_pointer;
			if(  exit -> link_type == LinkType::INDIRECT ){
					exit -> next = GLOBAL::next_address;
					VBlock * temp_vblock = sig_tc -> lookupVBlock( GLOBAL::tblock_stub -> getSPC() );
					temp_vblock -> setVbNextSpc( GLOBAL::next_address); 
			}

			if (exit -> why == ExitReason::SYSCALLEXIT) 
					(*sig_sh)();


			sig_entry = exit -> next;

			pre_tblock = GLOBAL::tblock_stub;
 
		}

        //UTIL::XTLOG("\nexit from doSignal\n\n");
}
